﻿using System;
using System.Windows.Forms;
using System.ComponentModel;
using System.Runtime.InteropServices;

using System.Collections.Generic;
using System.Linq;
using CrawlerCore;
using WCrawlerLib;
using mshtml;

//First define a new EventArgs class to contain the newly exposed data
public class NewWindow2EventArgs : CancelEventArgs
{

    object ppDisp;

    public object PPDisp
    {
        get { return ppDisp; }
        set { ppDisp = value; }
    }


    public NewWindow2EventArgs(ref object ppDisp, ref bool cancel)
        : base()
    {
        this.ppDisp = ppDisp;
        this.Cancel = cancel;
    }
}

public class DocumentCompleteEventArgs : EventArgs
{
    private object ppDisp;
    private object url;

    public object PPDisp
    {
        get { return ppDisp; }
        set { ppDisp = value; }
    }

    public object Url
    {
        get { return url; }
        set { url = value; }
    }

    public DocumentCompleteEventArgs(object ppDisp, object url)
    {
        this.ppDisp = ppDisp;
        this.url = url;

    }
}

public class CommandStateChangeEventArgs : EventArgs
{
    private long command;
    private bool enable;
    public CommandStateChangeEventArgs(long command, ref bool enable)
    {
        this.command = command;
        this.enable = enable;
    }

    public long Command
    {
        get { return command; }
        set { command = value; }
    }

    public bool Enable
    {
        get { return enable; }
        set { enable = value; }
    }
}


//Extend the WebBrowser control
public partial class ExtendedWebBrowser : WebBrowser
{
    AxHost.ConnectionPointCookie cookie;
    WebBrowserExtendedEvents events;


    //This method will be called to give you a chance to create your own event sink
    protected override void CreateSink()
    {
        //MAKE SURE TO CALL THE BASE or the normal events won't fire
        base.CreateSink();
        events = new WebBrowserExtendedEvents(this);
        cookie = new AxHost.ConnectionPointCookie(this.ActiveXInstance, events, typeof(DWebBrowserEvents2));

    }

    public object Application
    {
        get
        {
            IWebBrowser2 axWebBrowser = this.ActiveXInstance as IWebBrowser2;
            if (axWebBrowser != null)
            {
                return axWebBrowser.Application;
            }
            else
                return null;
        }
    }

    protected override void DetachSink()
    {
        if (null != cookie)
        {
            cookie.Disconnect();
            cookie = null;
        }
        base.DetachSink();
    }

    //This new event will fire for the NewWindow2
    public event EventHandler<NewWindow2EventArgs> NewWindow2;

    protected void OnNewWindow2(ref object ppDisp, ref bool cancel)
    {
        EventHandler<NewWindow2EventArgs> h = NewWindow2;
        NewWindow2EventArgs args = new NewWindow2EventArgs(ref ppDisp, ref cancel);
        if (null != h)
        {
            h(this, args);
        }
        //Pass the cancellation chosen back out to the events
        //Pass the ppDisp chosen back out to the events
        cancel = args.Cancel;
        ppDisp = args.PPDisp;
    }


    //This new event will fire for the DocumentComplete
    public event EventHandler<DocumentCompleteEventArgs> DocumentComplete;

    protected void OnDocumentComplete(object ppDisp, object url)
    {
        EventHandler<DocumentCompleteEventArgs> h = DocumentComplete;
        DocumentCompleteEventArgs args = new DocumentCompleteEventArgs(ppDisp, url);
        if (null != h)
        {
            h(this, args);
        }
        //Pass the ppDisp chosen back out to the events
        ppDisp = args.PPDisp;
        //I think url is readonly
    }

    //This new event will fire for the DocumentComplete
    public event EventHandler<CommandStateChangeEventArgs> CommandStateChange;

    protected void OnCommandStateChange(long command, ref bool enable)
    {
        EventHandler<CommandStateChangeEventArgs> h = CommandStateChange;
        CommandStateChangeEventArgs args = new CommandStateChangeEventArgs(command, ref enable);
        if (null != h)
        {
            h(this, args);
        }
    }


    //This class will capture events from the WebBrowser
    public class WebBrowserExtendedEvents : System.Runtime.InteropServices.StandardOleMarshalObject, DWebBrowserEvents2
    {
        ExtendedWebBrowser _Browser;
        public WebBrowserExtendedEvents(ExtendedWebBrowser browser)
        { _Browser = browser; }

        //Implement whichever events you wish
        public void NewWindow2(ref object pDisp, ref bool cancel)
        {
            _Browser.OnNewWindow2(ref pDisp, ref cancel);
        }

        //Implement whichever events you wish
        public void DocumentComplete(object pDisp, ref object url)
        {
            _Browser.OnDocumentComplete(pDisp, url);
        }

        //Implement whichever events you wish
        public void CommandStateChange(long command, bool enable)
        {
            _Browser.OnCommandStateChange(command, ref enable);
        }


    }
    [ComImport, Guid("34A715A0-6587-11D0-924A-0020AFC7AC4D"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch), TypeLibType(TypeLibTypeFlags.FHidden)]
    public interface DWebBrowserEvents2
    {
        [DispId(0x69)]
        void CommandStateChange([In] long command, [In] bool enable);
        [DispId(0x103)]
        void DocumentComplete([In, MarshalAs(UnmanagedType.IDispatch)] object pDisp, [In] ref object URL);
        [DispId(0xfb)]
        void NewWindow2([In, Out, MarshalAs(UnmanagedType.IDispatch)] ref object pDisp, [In, Out] ref bool cancel);
    }

    [ComImport, Guid("D30C1661-CDAF-11d0-8A3E-00C04FC9E26E"), TypeLibType(TypeLibTypeFlags.FOleAutomation | TypeLibTypeFlags.FDual | TypeLibTypeFlags.FHidden)]
    public interface IWebBrowser2
    {
        [DispId(100)]
        void GoBack();
        [DispId(0x65)]
        void GoForward();
        [DispId(0x66)]
        void GoHome();
        [DispId(0x67)]
        void GoSearch();
        [DispId(0x68)]
        void Navigate([In] string Url, [In] ref object flags, [In] ref object targetFrameName, [In] ref object postData, [In] ref object headers);
        [DispId(-550)]
        void Refresh();
        [DispId(0x69)]
        void Refresh2([In] ref object level);
        [DispId(0x6a)]
        void Stop();
        [DispId(200)]
        object Application { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
        [DispId(0xc9)]
        object Parent { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
        [DispId(0xca)]
        object Container { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
        [DispId(0xcb)]
        object Document { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
        [DispId(0xcc)]
        bool TopLevelContainer { get; }
        [DispId(0xcd)]
        string Type { get; }
        [DispId(0xce)]
        int Left { get; set; }
        [DispId(0xcf)]
        int Top { get; set; }
        [DispId(0xd0)]
        int Width { get; set; }
        [DispId(0xd1)]
        int Height { get; set; }
        [DispId(210)]
        string LocationName { get; }
        [DispId(0xd3)]
        string LocationURL { get; }
        [DispId(0xd4)]
        bool Busy { get; }
        [DispId(300)]
        void Quit();
        [DispId(0x12d)]
        void ClientToWindow(out int pcx, out int pcy);
        [DispId(0x12e)]
        void PutProperty([In] string property, [In] object vtValue);
        [DispId(0x12f)]
        object GetProperty([In] string property);
        [DispId(0)]
        string Name { get; }
        [DispId(-515)]
        int HWND { get; }
        [DispId(400)]
        string FullName { get; }
        [DispId(0x191)]
        string Path { get; }
        [DispId(0x192)]
        bool Visible { get; set; }
        [DispId(0x193)]
        bool StatusBar { get; set; }
        [DispId(0x194)]
        string StatusText { get; set; }
        [DispId(0x195)]
        int ToolBar { get; set; }
        [DispId(0x196)]
        bool MenuBar { get; set; }
        [DispId(0x197)]
        bool FullScreen { get; set; }
        [DispId(500)]
        void Navigate2([In] ref object URL, [In] ref object flags, [In] ref object targetFrameName, [In] ref object postData, [In] ref object headers);
        [DispId(0x1f7)]
        void ShowBrowserBar([In] ref object pvaClsid, [In] ref object pvarShow, [In] ref object pvarSize);
        [DispId(-525)]
        WebBrowserReadyState ReadyState { get; }
        [DispId(550)]
        bool Offline { get; set; }
        [DispId(0x227)]
        bool Silent { get; set; }
        [DispId(0x228)]
        bool RegisterAsBrowser { get; set; }
        [DispId(0x229)]
        bool RegisterAsDropTarget { get; set; }
        [DispId(0x22a)]
        bool TheaterMode { get; set; }
        [DispId(0x22b)]
        bool AddressBar { get; set; }
        [DispId(0x22c)]
        bool Resizable { get; set; }
    }
}

public class ExtendedForumBrowser : ExtendedWebBrowser
{
    public string _username_key;
    public string _password_key;
    public string _isLoginCookies_key;
    public string _isRememberMe_key;
    public string _LoginInput_key;

    public string _title_key = "subject";
    public string _message_key = "vB_Editor_001_textarea";
    public string _switchmode_key = "vB_Editor_001_cmd_switchmode";

    public bool isFinishLoadNewThread = false;
    public PostDetail objPostDetail;

    private Forum _objForum;
    public Forum objForum
    {
        get
        {
            return _objForum;
        }
        set
        {
            _objForum = value;
        }
    }

    private Post _objPost;
    public Post objPost
    {
        get
        {
            return _objPost;
        }
        set
        {
            _objPost = value;
        }
    }

    public void InitParams()
    {
        if (_objForum.ForumTypeId == (int)enumForumType.phpBB2)
        {
            _username_key = "username";
            _password_key = "password";
            _isLoginCookies_key = "userid";
            _isRememberMe_key = "autologin";
            _LoginInput_key = "log in";

            _title_key = "subject";
            _message_key = "message";
            _switchmode_key = "123";
        }
        else if(_objForum.ForumTypeId == (int)enumForumType.phpBB3)
        {
            _username_key = "username";
            _password_key = "password";
            _isLoginCookies_key = "sid";
            _isRememberMe_key = "autologin";
            _LoginInput_key = "login";

            _title_key = "subject";
            _message_key = "message";
            _switchmode_key = "123";
        }
        else if (_objForum.ForumTypeId == (int)enumForumType.vBulletin3x ||
                _objForum.ForumTypeId == (int)enumForumType.vBulletin4x)
        {
            _username_key = "navbar_username";
            _password_key = "navbar_password";
            _isLoginCookies_key = "sessionhash|userid|loggedin";
            _isRememberMe_key = "cb_cookieuser_navbar";
            _LoginInput_key = "log in";

            _title_key = "subject";
            if (_objForum.ForumTypeId == (int)enumForumType.vBulletin3x)
                _message_key = "vB_Editor_001_textarea";
            else
                _message_key = "vB_Editor_001_editor";
            _switchmode_key = "vB_Editor_001_cmd_switchmode";
        }
        else if (_objForum.ForumTypeId == (int)enumForumType.IPB31x ||
                _objForum.ForumTypeId == (int)enumForumType.IPB32x)
        {
            _username_key = "ips_username";
            _password_key = "ips_password";
            _isLoginCookies_key = "member_id";
            _isRememberMe_key = "inline_remember";
            _LoginInput_key = "sign in";

            _title_key = "topic_title";
            //editor_4f4d0fbcbbdf0
            //editor_4f4d153488f77 
            //_message_key = "cke_source cke_enable_context_menu"; 

            _switchmode_key = "vB_Editor_001_cmd_switchmode";
        }
    }

    /// <summary>
    /// References: 
    /// 1. http://stackoverflow.com/questions/6434687/waiting-for-ajax-load-using-web-browser-control
    /// 2. http://codingform.blogspot.com/2009/08/c-webbrowser-control-synchronization.html
    /// </summary>
    /// <param name="webBrControl"></param>
    public void WaitTillLoad()//ExtendedWebBrowser webBrControl)
    {
        WebBrowserReadyState loadStatus = WebBrowserReadyState.Uninitialized;
        //wait till beginning of loading next page 
        int waittime = 100000;
        int counter = 0;
        while (true)
        {
            try
            {
                loadStatus = (WebBrowserReadyState)this.ReadyState;
            }
            catch (Exception ex)
            {
                //throw ex;
            }

            System.Windows.Forms.Application.DoEvents();

            if ((counter > waittime) || (loadStatus == WebBrowserReadyState.Uninitialized) || (loadStatus == WebBrowserReadyState.Loading) || (loadStatus == WebBrowserReadyState.Interactive))
            {
                break;
            }
            counter++;
        }

        //wait till the page get loaded.
        counter = 0;
        while (true)
        {
            try
            {
                loadStatus = (WebBrowserReadyState)this.ReadyState;
            }
            catch (Exception ex)
            {
                //throw ex;
            }

            System.Windows.Forms.Application.DoEvents();

            if (loadStatus == WebBrowserReadyState.Complete && this.IsBusy != true)
            {
                break;
            }
            counter++;
        }
    }

    public bool IsLogin()
    {
        bool isLogin = false;
        List<string> cookieList = new List<string>();
        cookieList = this._isLoginCookies_key.Split(new char[] { '|' }).ToList();
        if (this.Document != null && this.Document.Cookie != null)
        {
            foreach (string cookie in this.Document.Cookie.Split(';'))
            {
                string name = cookie.Split('=')[0].ToLower();
                foreach (string cookieKey in cookieList)
                {
                    if (name.Contains(cookieKey))
                    {
                        isLogin = true;
                        break;
                    }
                }

                if (isLogin)
                    break;
            }
        }
        return isLogin;
    }

    /// <summary>
    /// Auto login & auto click if choose
    /// </summary>
    /// <param name="isClick"></param>
    /// <returns></returns>
    public bool DoLogin(bool isClick)
    {
        //========================================================================================================
        //Step 1: Do login
        ForumTemplateDetail objForumTemplateDetail = new ForumTemplateDetail();
        string loginUrl = objForumTemplateDetail.GetLoginUrl(_objForum.ForumId, _objForum.ForumTypeId);
        
        this.Navigate(_objForum.ForumUrl);
        //Wait complete
        while (this.ReadyState != WebBrowserReadyState.Complete)
        {
            System.Windows.Forms.Application.DoEvents();
        }
        //WaitTillLoad(this);

        bool isLogin = IsLogin();

        if (!isLogin)
        {
            try
            {
                this.Navigate(_objForum.ForumUrl + "/" + loginUrl);
                //Wait complete
                while (this.ReadyState != WebBrowserReadyState.Complete)
                {
                    System.Windows.Forms.Application.DoEvents();
                }
                //WaitTillLoad(this);
            
                //Fill username & pass
                this.Document.GetElementById(this._username_key).InnerText = objForum.Account.UserName;
                this.Document.GetElementById(this._password_key).InnerText = objForum.Account.Password;

                ((HTMLInputElement)this.Document.GetElementById(this._isRememberMe_key).DomElement).@checked = true;

                if (isClick)
                {
                    //Submit Login
                    //this.Document.Forms[0].InvokeMember("submit");
                    HtmlElementCollection elems = this.Document.GetElementsByTagName("input");
                    foreach (HtmlElement elem in elems)
                    {
                        if (elem.GetAttribute("value").ToLower() == this._LoginInput_key)
                        {
                            elem.InvokeMember("click");
                            WaitTillLoad();
                            isLogin = IsLogin();
                            break;
                        }
                    }
                }
            }
            catch
            {
                isLogin = true;
            }
        }
        return isLogin;
    }

    
    /// <summary>
    /// Load new thread url and auto fill content
    /// </summary>
    public void LoadNewThreadUrl()
    {
        //========================================================================================================
        //Step 2: Post a new thread
        string newThreadUrl, fValue, title, message;
        _objForum.GetPostNewThread_WebBrowser(_objPost.PostId, _objForum.ForumId, out objPostDetail, out newThreadUrl, out fValue, out title, out message);
        newThreadUrl = GeneralClass.GetFullUrl(objForum.ForumUrl, newThreadUrl.Replace("§","&sect")
                                                                              .Replace("[fid]", fValue.ToString())
                                                                              .Replace("&s=[sessionhash]", ""));
        //try load 3 times
        int count = 3;
        while ((count--) > 0)
        {
            this.Navigate(newThreadUrl);
            WaitTillLoad();
            //Wait complete
            while (this.ReadyState != WebBrowserReadyState.Complete)
            {
                System.Windows.Forms.Application.DoEvents();
            }

            //Load success
            if (this.Url.AbsoluteUri == newThreadUrl)
            {
                break;
            }
        }

        //========================================================================================================
        //Step 3: Fill content in new thread
        if (this.Url.AbsoluteUri == newThreadUrl)
        {
            count = -1;
            System.Windows.Forms.HtmlDocument doc = this.Document;

            if (this.Document != null)
            {
                HtmlElementCollection elems = this.Document.GetElementsByTagName("input");
                foreach (HtmlElement elem in elems)
                {
                    if (elem.GetAttribute("name") == _title_key)
                        elem.SetAttribute("value", title);
                }

                HtmlElement ele = this.Document.GetElementById(_message_key);
                if (ele != null)
                {
                    if (!ele.OuterHtml.ToLower().Contains("display: none"))
                        ele.InnerText = message;
                    else
                    {
                        this.Document.GetElementById(_switchmode_key).InvokeMember("click");
                        WaitTillLoad();
                        ele.InnerText = message;
                    }
                }
                else //case for vBulletin4x and IPB32x
                {
                    elems = this.Document.GetElementsByTagName("textarea");
                    if (elems.Count > 1)
                    {
                        foreach (HtmlElement elem in elems)
                        {
                            if (!ele.OuterHtml.ToLower().Contains("display: none"))
                            {
                                ele.InnerText = message;
                                break;
                            }
                        }
                    }
                    else
                    {
                        foreach (HtmlElement elem in elems)
                        {
                            if (!ele.OuterHtml.ToLower().Contains("display: none"))
                            {
                                ele.InnerText = message;
                                break;
                            }
                            else
                            {
                                this.Document.GetElementById(_switchmode_key).InvokeMember("click");
                                WaitTillLoad();
                                ele.InnerText = message;
                            }
                        }   
                    }
                }
                //Finish load new thread
                isFinishLoadNewThread = true;
            }
        }
    }

    public void AutoCreatePost()
    {
        try
        {
            InitParams();

            //if (!DoLogin(true))
              //  return;

            LoadNewThreadUrl();

            //SaveThreadPost();
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    public void SaveThreadPost()
    {
        string responseUrl = this.Url.AbsoluteUri;
        responseUrl = responseUrl.Replace(objForum.ForumUrl, "");
        //if postUrl contains "/" at first position then remove it
        if (responseUrl.Substring(0, 1).Contains("/"))
            responseUrl = responseUrl.Substring(1);

        objForum.SaveThreadPost(objForum.ForumId, objPostDetail, responseUrl, null, null);
    }
}
